/*
 * Copyright (c) 2005, The haXe Project Contributors
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE HAXE PROJECT CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE HAXE PROJECT CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 */

/**
	The Reflect API is a way to manipulate values dynamicly through an
	abstract interface in an untyped manner. Use with care.
**/
class Reflect {

	/**
		Creates an empty object.
	**/
	public static inline function empty() : {} {
		return untyped
		#if flash9
			__new__(__global__["Object"])
		#else flash
			__new__(_global["Object"])
		#else neko
			__dollar__new(null)
		#else js
			__js__("{}")
		#else hllua
			__lua__("{}")
		#else error
		#end
			;
	}

	/**
		Tells if an object has a field set. This doesn't take into account the object prototype (class methods).
	**/
	public static function hasField( o : Dynamic, field : String ) : Bool {
		untyped{
		#if flash9
			return o.hasOwnProperty( field );
		#else flash
			return this["hasOwnProperty"]["call"](o,field);
		#else js
			if( o.hasOwnProperty != null )
				return o.hasOwnProperty(field);
			var arr = fields(o);
			for( t in arr )
				if( t == field ) return true;
			return false;
		#else neko
			return __dollar__typeof(o) == __dollar__tobject && __dollar__objfield(o,__dollar__hash(field.__s));
		#else hllua
			return hasOwnProperty(o, field);
		#else error
		#end
		}
	}

	/**
		Returns the field of an object, or null if [o] is not an object or doesn't have this field.
	**/
	public static #if !hllua inline #end function field( o : Dynamic, field : String ) : Dynamic {
		untyped
		#if flash
			return o[field]
		#else js
			try return o[field] catch( e : Dynamic ) { return null; }
		#else neko
			return if( __dollar__typeof(o) != __dollar__tobject )
				null;
			else
				__dollar__objget(o, __dollar__hash(field.__s))
		#else hllua
			return if(__typeof__(o) != "table") null;
			else o[field]
		#else error
		#end
			;
	}

	/**
		Set an object field value.
	**/
	public static inline function setField( o : Dynamic, field : String, value : Dynamic ) : Void {
		untyped
		#if flash
			o[field] = value;
		#else js
			o[field] = value;
		#else neko
			if( __dollar__typeof(o) == __dollar__tobject )
				__dollar__objset(o,__dollar__hash(field.__s),value);
		#else hllua
			o[field] = value;
		#else error
		#end
	}

	/**
		Call a method with the given object and arguments.
	**/
	public static inline function callMethod( o : Dynamic, func : Dynamic, args : Array<Dynamic> ) : Dynamic {
		return untyped
		#if flash9
			func.apply(o,args)
		#else flash
			func["apply"](o,args)
		#else js
			func.apply(o,args)
		#else neko
			__dollar__call(func,o,args.__neko())
		#else hllua
			call_method(o, func, args)
		#else error
		#end
			;
	}

	/**
		Returns the list of fields of an object, excluding its prototype (class methods).
	**/
	public static function fields( o : Dynamic ) : Array<String> {
		if( o == null ) return new Array();
		untyped {
		#if flash9
			var a : Array<String> = __keys__(o);
			var i = 0;
			while( i < a.length ){
				if( !o.hasOwnProperty(a[i]) )
					a.splice(i,1);
				else
					++i;
			}
			return a;
		#else flash
			var a : Array<String> = __keys__(o);
			var i = 0;
			while( i < a.length ) {
				if( !a["hasOwnProperty"]["call"](o,a[i]) )
					a.splice(i,1);
				else
					++i;
			}
			return a;
		#else js
			var a = new Array();
			if( untyped o.hasOwnProperty ) {
				untyped __js__("
					for(var i in o)
						if( o.hasOwnProperty(i) )
							a.push(i);
				");
			} else {
				var t;
				try{ t = o.__proto__; } catch( e : Dynamic ) { t = null; }
				if( t != null )
					o.__proto__ = null;
				untyped __js__("
					for(var i in o)
						if( i != \"__proto__\" )
							a.push(i);
				");
				if( t != null )
					o.__proto__ = t;
			}
			return a;
		#else neko
			if( __dollar__typeof(o) != __dollar__tobject )
				return new Array<String>();
			else {
				var a = __dollar__objfields(o);
				var l = __dollar__asize(a);
				for(i in 0 ... l)
					a[i] = new String(__dollar__field(a[i]));
				
				return Array.new1(a,l);
			}
		#else hllua
			var a : Array<String>;
			a = untyped __keys__(untyped __fields__(o));
			return a;
		#else error
		#end
		}
	}

	/**
		Tells if a value is a function or not.
	**/
	public static function isFunction( f : Dynamic ) : Bool {
		#if flash9
			return untyped __typeof__(f) == "function";
		#else flash
			return untyped __typeof__(f) == "function" && f.__name__ == null;
		#else js
			var f = f;
			return untyped __js__("typeof(f)") == "function" && f.__name__ == null;
		#else neko
			return untyped __dollar__typeof(f) == __dollar__tfunction;
		#else hllua
			return untyped __typeof__(f) == "function";
		#else error
		#end
	}

	/**
		Generic comparison function, does not work for methods, see [compareMethods]
	**/
	public static function compare<T>( a : T, b : T ) : Int {
		return ( a == b ) ? 0 : (((cast a) > (cast b)) ? 1 : -1);
	}

	/**
		Compare two methods closures. Returns true if it's the same method of the same instance.
		Does not work on Neko platform.
	**/
	public static function compareMethods( f1 : Dynamic, f2 : Dynamic ) : Bool {
		if( f1 == f2 )
			return true;
		if( !isFunction(f1) || !isFunction(f2) )
			return false;
		#if neko
		return false; // compare already done
		#else flash9
		return false; // VM-level closures
		#else flash
		return untyped f1["f"] == f2["f"] && f1["o"] == f2["o"] && f1["f"] != null;
		#else js
		return f1.scope == f2.scope && f1.method == f2.method && f1.method != null;
		#else hllua
		return false;
		#else true
		return
		#end
	}

	/**
		Tells if a value is an object or not.

	**/
	public static function isObject( v : Dynamic ) : Bool untyped {
		#if neko
		return __dollar__typeof(v) == __dollar__tobject && v.__enum__ == null;
		#else flash9
		if( v == null )
			return false;
		var t = __typeof__(v);
		if( t == "object" ) {
			try {
				if( v.__enum__ == true )
					return false;
			} catch( e : Dynamic ) {
			}
			return true;
		}
		return (t == "string");
		#else flash
		var t = __typeof__(v);
		return (t == "string" || (t == "object" && !v.__enum__) || (t == "function" && v.__name__ != null));
		#else js
		if( v == null )
			return false;
		var t = __js__("typeof(v)");
		return (t == "string" || (t == "object" && !v.__enum__) || (t == "function" && v.__name__ != null));
		#else hllua
		return __typeof__(v) == "table" && v.__enum__ == null;
		#else error
		#end
	}

	/**
		Delete an object field.
	**/
	public static function deleteField( o : Dynamic, f : String ) : Bool untyped {
		#if flash9
			return if( o.hasOwnProperty(f) ) {
				__delete__(o,f);
				true;
			}
			else false;
		#else flash
			return if( this["hasOwnProperty"]["call"](o,f) ) {
				__delete__(o,f);
				true;
			}
			else false;
		#else js
			return if( hasField(o,f) ) {
				__js__("delete")(o[f]);
				true;
			}
			else false;
		#else neko
			return __dollar__objremove(o,__dollar__hash(f.__s));
		#else hllua
			return if(hasOwnProperty(o, f)) {
				__delete__(o, f);
				true;
			}
			else false;
		#else error
		#end
	}

	/**
		Make a copy of the fields of an object.
	**/
	public static inline function copy<T>( o : T ) : T {
		#if neko
		return untyped __dollar__new(o);
		#else true
		var o2 = cast empty();
		for( f in Reflect.fields(o) )
			Reflect.setField(o2,f,Reflect.field(o,f));
		return o2;
		#end
	}

	/**
		Transform a function taking an array of arguments into a function that can
		be called with any number of arguments.
	**/
	public static function makeVarArgs( f : Array<Dynamic> -> Dynamic ) : Dynamic {
		#if neko
		return untyped __dollar__varargs(function(a) { return f(Array.new1(a,__dollar__asize(a))); });
		#else flash9
		return function(__arguments__) { return f(__arguments__); };
		#else js
		return function() untyped {
			var a = new Array();
			for( i in 0...arguments.length )
				a.push(arguments[i]);
			return f(a);
		};
		#else flash
		return function() { return f(untyped __arguments__); };
		#else hllua
		return untyped __lua__("(function(self,...) arg.n = nil return f(self,Array:new(arg)) end)");
		#end
	}

#if hllua
	static var hasOwnProperty = lua.Lib.load("Haxe","hasOwnProperty");
	static var call_method = lua.Lib.load("Haxe", "callMethod");
#end
}
